/*
 * Contains class for banks official sites currency rates data analysis
 */
package rates_analysis;

import generated_entities.*;
import generated_facades.BanksInRegionsFacade;
import generated_facades.ErrorReportsFacade;
import generated_facades.RatesFacade;
import generated_facades.TemplatesFacade;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ejb.EJB;
import javax.ejb.Schedule;
import javax.ejb.Stateless;


/**
 *
 * @author aav
 */
@Stateless
public class RatesAnalyzerSessionBean implements RatesAnalyzerSessionBeanLocal {
    @EJB
    private ErrorReportsFacade errorReportsFacade;
    @EJB
    private BanksInRegionsFacade banksInRegionsFacade;
    @EJB
    private RatesFacade ratesFacade;
    @EJB
    private TemplatesFacade templatesFacade;
    
    /**
     * Connects to specified URL resource
     * @param urlString URL
     * @return character stream reader
     * @throws RatesAnalysisException if URL is not valid or IO error happened
     */
    private BufferedReader connectToUrl(String urlString) throws RatesAnalysisException {
        try {
            return new BufferedReader(new InputStreamReader((new URL(urlString)).openStream()));
        } catch (MalformedURLException ex) {
            Logger.getLogger(RatesAnalyzerSessionBean.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            throw new RatesAnalysisException("URL не распознан: \"" + urlString + "\"");
        } catch (IOException ex) {
            Logger.getLogger(RatesAnalyzerSessionBean.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            throw new RatesAnalysisException("Ошибка обращения по URL: \"" + urlString + "\"");
        }
    }
    
    @Override
    public void testURL(BanksInRegions bankInRegion) throws RatesAnalysisException {
        connectToUrl(bankInRegion.getRatesPageUrl());
    }
    
    /**
     * Finds first regexp match at content specified by resourse url 
     * @param urlText resouce url
     * @param regexp regexp
     * @return rate value
     * @throws RatesAnalysisException if method could not parse urlString or IO error happened
     */
    private String analyze(String urlString, String regexp) throws RatesAnalysisException{
        String httpResponseLine, httpResponse = "";
        BufferedReader bufferedReader = connectToUrl(urlString);
        try {
            while (null != (httpResponseLine = bufferedReader.readLine())) {
                httpResponse += httpResponseLine;
            }
        } catch (IOException ex) {
            Logger.getLogger(RatesAnalyzerSessionBean.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            throw new RatesAnalysisException("Ошибка чтения содержимого по адресу: \"" + urlString + "\"");
        }
        Matcher matcher = Pattern.compile(">\\s*").matcher(httpResponse);
        httpResponse = matcher.replaceAll(">");
        matcher = Pattern.compile("\\s*<").matcher(httpResponse);
        httpResponse = matcher.replaceAll("<");
        matcher = Pattern.compile(regexp).matcher(httpResponse);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }
    
    @Override
    public Rates extractRatesForTemplate(Templates template) throws RatesAnalysisException {
        Rates rates = null;
        String ratesPageUrl = template.getBankRegionId().getRatesPageUrl();
        Double purchaseRate = null;
        String rateText = null;
        try {
            rateText = analyze(ratesPageUrl, template.getPurchaseTemplateText()).replace(",", ".");
            purchaseRate = Double.parseDouble(rateText);
        } catch (NullPointerException ex) {
            throw new RatesAnalysisException("Не найдено ни одного совпадения для регулярного выражения выборки цены покупки валюты: " + template.getPurchaseTemplateText() + " по адресу: \"" + template.getBankRegionId().getRatesPageUrl() + "\"");
        } catch (NumberFormatException ex) {
            throw new RatesAnalysisException("Найденное совпадение " + rateText + " для регулярного выражения выборки цены покупки валюты: " + template.getPurchaseTemplateText() + " по адресу: \"" + template.getBankRegionId().getRatesPageUrl() + "\" не является вещественным числом");
        }
        Double saleRate = null;
        try {
            rateText = analyze(ratesPageUrl, template.getSaleTemplateText()).replace(",", ".");
            saleRate = Double.parseDouble(rateText);
        } catch (NullPointerException ex) {
            throw new RatesAnalysisException("Не найдено ни одного совпадения для регулярного выражения выборки цены продажи валюты: " + template.getSaleTemplateText() + " по адресу: \"" + template.getBankRegionId().getRatesPageUrl() + "\"");
        } catch (NumberFormatException ex) {
            throw new RatesAnalysisException("Найденное совпадение " + rateText + " для регулярного выражения выборки цены продажи валюты: " + template.getSaleTemplateText() + " по адресу: \"" + template.getBankRegionId().getRatesPageUrl() + "\" не является вещественным числом");
        }
        java.util.Date date = new java.util.Date();
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        RatesPK ratesPK = new RatesPK(template.getBankRegionId().getBankRegionId().toBigInteger(), template.getCurrencyId().getCurrencyId().toBigInteger(), java.sql.Date.valueOf(dateFormat.format(date)));
        rates = ratesFacade.find(ratesPK);
        if (null == rates) {
            rates = new Rates(ratesPK);
        }    
        rates.setPurchaseRate(purchaseRate);
        rates.setSaleRate(saleRate);
        rates.setCurrencies(template.getCurrencyId());
        rates.setBanksInRegions(template.getBankRegionId());
        return rates;
    }
    
    /**
     * Creates rates record in db or replaces existing if update dates are equal
     * 
     * @param rates will be saved into db
     */
    private void saveRatesForTemplate(Rates rates) {
        if (null == ratesFacade.find(rates.getRatesPK())) {
            ratesFacade.create(rates);
        } else {
            ratesFacade.edit(rates);
        }
    }
    
    /**
     * Creates error report record and saves it into db
     * @param ex raised exception
     * @param template during rates update which exception raised
     */
    private void createErrorReport(Exception ex, Templates template) {
        ErrorReports errorReport = new ErrorReports();
        errorReport.setErrorReportText(ex.getMessage());
        errorReport.setTemplateId(template);
        errorReport.setRaiseTime(new java.util.Date());
        errorReportsFacade.create(errorReport);
    }
    
   @Override
   public Rates updateRatesForTemplate(Templates template) throws RatesAnalysisException {
       Rates rates = null;
       try {   
            rates = extractRatesForTemplate(template);
        } catch (RatesAnalysisException ex) {
            Logger.getLogger(RatesAnalyzerSessionBean.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            createErrorReport(ex, template);
            throw ex;
        }
       saveRatesForTemplate(rates);
       return rates;
    } 
   
    @Override
    public int updateRates() {
        ListIterator<Templates> listIterator = templatesFacade.findAll().listIterator();
        Templates template;
        int errorsCount = 0;
        while (listIterator.hasNext()) {
            template = listIterator.next();
            try {
                saveRatesForTemplate(extractRatesForTemplate(template));
            } catch (RatesAnalysisException ex) {
                ++errorsCount;
                Logger.getLogger(RatesAnalyzerSessionBean.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                createErrorReport(ex, template);
            }
        }
        return errorsCount;
    } 
    
    @Override
    @javax.ejb.Schedule(minute="*", hour="*")
    public void updateRatesScheduled() {
        updateRates();
    }
}
